package fi.iki.juri.units.generator;

import java.io.StringWriter;
import java.util.Collection;

import fi.iki.juri.units.generator.ParsedTranslations.Translation;

public class JavaGenerator implements CodeGenerator {
	private final String className;
	private final String packageName;
	
	public JavaGenerator(final String moduleName) {
		final int lastDot = moduleName.lastIndexOf('.');
		this.className = moduleName.substring(lastDot+1);
		this.packageName = moduleName.substring(0, lastDot);
	}
	
	private void writeClassTail(final StringWriter klassWriter) {
		final String tail =	"}\n";
		klassWriter.write(tail);
	}

	private void writeClassHead(final StringWriter klassWriter) {
		final String head = "package " + packageName + ";\n" + 
				"\n" + 
				"import java.util.HashMap;\n" + 
				"import java.util.Map;\n" + 
				"\n" + 
				"import fi.iki.juri.units.Unit;\n" +
				"import fi.iki.juri.units.Calculation;\n" +
				"\n" +
				"/**\n" + 
				" * Initialize the recognized units.\n" + 
				" *\n" + 
				" * This class has been generated by fi.iki.juri.units.generator.Generator. Do not edit.\n" + 
				" */\n" + 
				"public class " + className + " {\n" + 
				"	private static void createUnit(final HashMap<String,Unit> map, final String name) {\n" + 
				"		map.put(name, new Unit(name));\n" + 
				"	}\n" + 
				"	\n" + 
				"	private static void aliasUnit(final HashMap<String,Unit> map, final String original, final String alias) {\n" + 
				"		map.put(alias, map.get(original));\n" + 
				"	}\n" + 
				"	\n";
		klassWriter.write(head);
	}
	
	private void writeCreateUnitsMapHead(final StringWriter klassWriter, int mapSize) {
		// According to the HashMap documentation, higher load factor increases
		// lookup cost. Using a perfect hash generator here would be a good idea.
		final String methodHead =
			"	public static Map<String, Unit> createUnitsMap() {\n" + 
			"		final HashMap<String, Unit> map = new HashMap<String, Unit>(%d, 0.75F);\n";
		klassWriter.write(String.format(methodHead, (int)(mapSize/0.75)+1));
	}

	private void writeCreateUnitsMapTail(final StringWriter klassWriter) {
		final String methodTail =
			"		return map;\n" + 
			"	}\n";
		klassWriter.write(methodTail);
	}
	
	private void writeGetTranslationsHead(final StringWriter klassWriter) {
		final String methodHead =
			"	public static Map<String,? extends Map<String,Unit>> getTranslations(final Map<String,Unit> units) {\n" +
			"   	final HashMap<String,HashMap<String,Unit>> translations = new HashMap<String,HashMap<String,Unit>>();\n" +
			"		HashMap<String,Unit> trans;\n";
			klassWriter.write(methodHead);
	}
	
	private void writeGetTranslationsTail(final StringWriter klassWriter) {
		final String methodTail =
			"		return translations;\n" +
			"	}\n";
		klassWriter.write(methodTail);
	}

	public String generate(Collection<ParsedUnit> units, Collection<ParsedTranslations> translations) {
		final StringWriter klassWriter = new StringWriter();
		writeClassHead(klassWriter);
		final StringBuilder createUnits = new StringBuilder(1024);
		final StringBuilder deriveUnits = new StringBuilder(1024);
		int mapSize = 0;
		for (final ParsedUnit unit : units) {
			createUnits.append(String.format("createUnit(map, \"%s\");\n", unit.getId()));
			mapSize++;
			for (final String alias : unit.getAliases()) {
				createUnits.append(String.format("aliasUnit(map, \"%s\", \"%s\");\n", unit.getId(), alias));
			}
			if (unit.getDerive() != null) {
				deriveUnits.append(String.format("map.get(\"%s\").deriveUnit(map.get(\"%s\"), ",  
						unit.getId(), unit.getDerive()));
				final String fromExpr = unit.getFromFormula() != null ? unit.getFromFormula() : (unit.getToFormula() != null ? GeneratorTools.reverseExpression(unit.getToFormula()) : "");
				final String toExpr = unit.getToFormula() != null ? unit.getToFormula() : (unit.getFromFormula() != null ? GeneratorTools.reverseExpression(unit.getFromFormula()) : "");
				deriveUnits.append(String.format("new Calculation() { public double perform(double x) { return %s; } public String toString() { return \"%s\"; }}, ", GeneratorTools.postfix2infix("x " + fromExpr, "x"), fromExpr));
				deriveUnits.append(String.format("new Calculation() { public double perform(double x) { return %s; } public String toString() { return \"%s\"; }}", GeneratorTools.postfix2infix("x " + toExpr, "x"), toExpr));
				deriveUnits.append(");\n");
			}
		}
		writeCreateUnitsMapHead(klassWriter, mapSize);
		klassWriter.write(createUnits.toString());
		klassWriter.write(deriveUnits.toString());
		writeCreateUnitsMapTail(klassWriter);
		writeGetTranslationsHead(klassWriter);
		for (final ParsedTranslations trans : translations) {
			klassWriter.write(String.format("trans = new HashMap<String,Unit>();\n"));
			klassWriter.write(String.format("translations.put(\"%s\", trans);\n", trans.getLocale()));
			for (final Translation t : trans.getTranslations()) {
				klassWriter.write(String.format("trans.put(\"%s\", units.get(\"%s\"));\n", t.getTranslation(), t.getUnit()));
			}
		}
		writeGetTranslationsTail(klassWriter);
		writeClassTail(klassWriter);
		return klassWriter.toString();
	}

	public String outputName() {
		return className + ".java";
	}
	
}
